﻿using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;
using log4net;
using MyLib.IO;
using MyLib.Web.Properties;

namespace MyLib.Web.HttpHandlers {
	/// <summary>
	/// 
	/// </summary>
	public class FileHandler : IHttpHandler {
		/// <summary>
		/// Internal logger.
		/// </summary>
		private ILog m_logger = LogManager.GetLogger(typeof(FileHandler));

		#region IHttpHandler Members

		/// <summary>
		/// 
		/// </summary>
		public bool IsReusable {
			get { return true; }
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		public void ProcessRequest(HttpContext context) {
			var csection = context.GetSection("mylib.web.fileHandler") as FileHandlerConfigSection;
			if (csection == null) {
				csection = new FileHandlerConfigSection();
			}

			if (csection.AnonymousUsersDenied && !context.Request.IsAuthenticated) {
				WriteErrorMessage(context, string.Format(Main.file_access_denied, context.Request.RawUrl));
				return;
			}

			var fp = new FileSystemProvider();
			try {
				var fileId = context.Request["file_id"];

				var ofile = fp.GetFileObject(fileId);
				if (ofile == null) {
					WriteErrorMessage(context, string.Format(Main.file_does_not_exist, context.Request.RawUrl));
					return;
				}

				WriteFile(context, ofile.Name, ofile.Size, ofile.Content);
			} catch (Exception ex) {
				m_logger.Warn("Cannot write file to response.", ex);

				WriteErrorMessage(context,
					string.Format(Main.file_cannot_download, ex.Message));
			}
		}

		#endregion

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <param name="fileName"></param>
		/// <param name="size"></param>
		/// <param name="content"></param>
		private void WriteFile(HttpContext context, string fileName, long size, byte[] content) {
			fileName = RegularFileNameForDownload(fileName);

			var contentType = GetContentTypeByFileName(fileName);

			fileName = EncodeFileNameForDownload(fileName, context);

			context.Response.Clear();
			context.Response.AppendHeader("Content-Type", contentType + ";charset=utf-8");
			context.Response.AppendHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
			context.Response.AppendHeader("Content-Length", size.ToString());

			context.Response.OutputStream.Write(content, 0, content.Length);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <param name="message"></param>
		private void WriteErrorMessage(HttpContext context, string message) {
			var bytes = Encoding.UTF8.GetBytes(message);

			context.Response.Clear();
			context.Response.AppendHeader("Content-Type", "text/plain;charset=utf-8");
			context.Response.AppendHeader("Content-Disposition", "attachment; filename=\"error.txt\"");
			context.Response.AppendHeader("Content-Length", bytes.Length.ToString());
			context.Response.AppendHeader("Date", new DateTime().ToUniversalTime().ToString("R"));

			context.Response.OutputStream.Write(bytes, 0, bytes.Length);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="fileName"></param>
		/// <returns></returns>
		private string RegularFileNameForDownload(string fileName) {
			var ics = Path.GetInvalidFileNameChars();

			var buf = new char[fileName.Length];
			var j = 0;
			for (int i = 0; i < fileName.Length; ++i) {
				var c = fileName[i];

				if (ics.Contains(c))
					continue;

				buf[j++] = c;
			}

			return new string(buf, 0, j);
		}

		/// <summary>
		/// Encode file-name for download
		/// </summary>
		/// <param name="fileName"></param>
		/// <param name="context"></param>
		/// <returns></returns>
		private string EncodeFileNameForDownload(string fileName, HttpContext context) {
			// IE bug
			const int IE_BUG_HEADER_MAX_LENGTH = 150;

			bool isIE = false;
			bool isFirefox = false;

			var browser = context.Request.Browser;
			if (!string.IsNullOrEmpty(browser.Browser)) {
				isIE = browser.Browser == "IE";
				isFirefox = browser.Browser == "Firefox";
			}

			string encodedFileName = string.Empty;
			if (isIE) {
				// Use Url.Encode method for IE.
				encodedFileName = HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8);
				if (encodedFileName.EndsWith("%0A")) {
					encodedFileName = encodedFileName.Substring(0, encodedFileName.Length - 3);
				}

				var ieVersion = browser.MajorVersion * 100 + browser.MinorVersion;

				if (encodedFileName.Length > IE_BUG_HEADER_MAX_LENGTH
					&& ieVersion < 601 /*IE 6.1(SP1)*/) {
					// Too long.
					string ext = System.IO.Path.GetExtension(fileName);

					int limit = IE_BUG_HEADER_MAX_LENGTH - ext.Length;

					var mainFileName
						= System.IO.Path.GetFileNameWithoutExtension(fileName);

					// Truncate file-name.
					mainFileName = mainFileName
						.Substring(0, Math.Min(mainFileName.Length, limit / 9));

					// Re-encode file-name.
					encodedFileName = HttpUtility.UrlEncode(mainFileName + ext, System.Text.Encoding.UTF8);
					if (encodedFileName.EndsWith("%0A")) {
						encodedFileName = encodedFileName.Substring(0, encodedFileName.Length - 3);
					}
				}
			} else if (isFirefox) {
				// Use Base64 for RFC.
				encodedFileName = "=?UTF-8?B?" + EncodeToBase64(fileName) + "?=";
			} else {
				encodedFileName = fileName;
			}

			return encodedFileName;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="src"></param>
		/// <param name="encoding"></param>
		/// <returns></returns>
		private static string EncodeToBase64(string src, Encoding encoding = null) {
			if (encoding == null)
				encoding = Encoding.UTF8;

			var bytes = encoding.GetBytes(src);

			try {
				return Convert.ToBase64String(bytes, Base64FormattingOptions.None);
			} catch {
				return string.Empty;
			}
		} // end of EncodeToBase64.

		/// <summary>
		/// 
		/// </summary>
		/// <param name="fileName"></param>
		/// <returns></returns>
		private static string GetContentTypeByFileName(string fileName) {
			var ext = System.IO.Path.GetExtension(fileName).ToUpper();
			if (ext == ".GIF") {
				return "image/gif";
			} else if (ext == ".PNG") {
				return "image/png";
			} else if (ext == ".JPG") {
				return "image/jpeg";
			} else if (ext == ".JPEG") {
				return "image/jpeg";
			} else if (ext == ".BMP") {
				return "image/bitmap";
			} else if (ext == ".TXT") {
				return "text/plain";
			} else if (ext == ".DOC") {
				return "application/msword";
			} else if (ext == ".PDF") {
				return "application/pdf";
			} else if (ext == ".XLS") {
				return @"application/vnd.ms-excel";
			} else if (ext == ".DOCX") {
				return @"application/vnd.openxmlformats-officedocument.wordprocessingml.document";
			} else if (ext == ".XLSX") {
				return @"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
			} else if (ext == ".ZIP") {
				return @"application/zip";
			} else if (ext == ".RAR") {
				return @"application/x-rar-compressed";
			} else {
				return @"application/octet-stream";
			}
		} // end of GetContentTypeByFileName.

	} // end of FileHandler.
}
